package com.itheima.factory;

import com.itheima.dao.RoleDao;
import com.itheima.dao.UserDao;
import com.itheima.domain.Role;
import com.itheima.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.*;
import java.util.*;

/**
 * 定义一个工厂对象，用于创建所有的service
 *
 * bean：在软件开发领域，他被认为是组成应用的组件。
 * JavaBean：用java语言编写的组件。
 *
 * BeanFactory就是用于创建JavaBean对象的。
 * 这里面的JavaBean：
 *      包含：表现层控制器对象（Servlet),业务层对象，和持久层对象以及实体类对象。
 *
 *      由于实体类每次的数据可能不一样，所以一般不会交由工厂创建。
 *      一般只需要创建一次的对象，就适合使用工厂创建。（例如：表现层，业务层和持久层对象）
 *
 *
 *
 *
 *  此类，要完成对service实现类的创建。
 *  创建时机：
 *      应该在字节码进入虚拟机时就创建好。因此，要写在静态代码块中。
 *  创建之后：
 *      找个地方存起来，以备后面使用时直接获取
 *
 *  提供一个方法，让servlet在使用时，可以根据名称获取到自己想要的service对象
 */
public class BeanFactory {

    //定义读取配置文件的对象
    private static ResourceBundle bundle;
    //定义一个容器，用于存放创建出来的对象
    private static Map<String,Object> beans;
    
    //使用静态代码块读取配置文件
    static {
        try{
            //实例化容器
            beans = new HashMap<String,Object>();
            //获取bundle对象
            bundle = ResourceBundle.getBundle("bean");
            //获取配置文件中的所有key
            Enumeration<String> keys = bundle.getKeys();
            //遍历keys
            while (keys.hasMoreElements()){
                //取出每个keys
                String key = keys.nextElement();
                //根据key取出全限定类名
                String classPath = bundle.getString(key);
                //根据全限定类名，反射创建对象
                Object value = Class.forName(classPath).newInstance();
                //把创建出来的对象存入容器中
                beans.put(key,value);
            }
        }catch (Exception e){
            e.printStackTrace();
            throw new ExceptionInInitializerError("初始化容器失败！");
        }
    }

    /**
     * 根据名称获取对应的对象
     * @param beanName
     * @return
     */
     public static Object getBean(String beanName){
        //获取bean对象
        final Object bean = beans.get(beanName);//被代理对象
        //创建bean对象的代理对象
        Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 执行被代理对象的任何方法，都会经过该方法。
                     * @param proxy      代理对象的引用（一般用不上）
                     * @param method     当前正在执行的被代理对象方法
                     * @param args       执行被代理对象方法所需的参数
                     * @return           和被代理对象方法具有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //定义sqlSession
                        SqlSession sqlSession = null;
                        //定义返回值
                        Object rtValue = null;
                        try{
                            //获取Dao对象
                            sqlSession = MybatisUtils.openSession();

                            //RoleDao roleDao = sqlSession.getMapper(RoleDao.class);
                            //UserDao userDao = sqlSession.getMapper(UserDao.class);
                            //获取当前被代理对象的字节码
                            Class beanClass = bean.getClass();
                            //获取当前字节码中声明的全部字段
                            Field[] fields = beanClass.getDeclaredFields();
                            //遍历所有的类成员
                            for(Field field : fields){
                                //判断当前的field是不是dao
                                Class type = field.getType();//field.getType()获取的就是当前字段声明时的类型
                                String typePackage = type.getPackage().toString();//得到是当前声明type所在包的名称字符串
                                if(typePackage.contains("com.itheima.dao")){
                                    //解除当前field的私有限制
                                    field.setAccessible(true);
                                    //通过字段反射的方式给field赋值
                                    field.set(bean,sqlSession.getMapper(type));
                                }
                            }

                            //执行方法
                            rtValue = method.invoke(bean,args);

                            //提交事务
                            MybatisUtils.commit(sqlSession);
                        }catch (Exception e){
                            //回滚事务
                            MybatisUtils.rollback(sqlSession);
                            e.printStackTrace();
                        }finally {
                            //释放资源
                            MybatisUtils.close(sqlSession);
                        }
                        return rtValue;
                    }
                });
        //返回代理对象
        return  proxyBean;//代理对象
    }
}
